package resolver

import (
	"context"
	"errors"
	"fmt"
	"runtime"
	"strings"

	"gitee.com/zawei/yggdrasil/pkg/log"
	"gitee.com/zawei/yggdrasil/pkg/registry"
	"google.golang.org/grpc/attributes"
	"google.golang.org/grpc/resolver"
)

type baseBuilder struct {
	registry registry.Registry
}

func Register(registry registry.Registry) {
	resolver.Register(&baseBuilder{
		registry: registry,
	})
}

// Build ...
func (b *baseBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
	endpoint := strings.Split(target.Endpoint, "@")
	if len(endpoint) == 0 {
		return nil, errors.New("endpoint format error")
	}
	group := ""
	name := ""
	if len(endpoint) == 1 {
		name = endpoint[0]
	} else {
		group = endpoint[0]
		name = endpoint[1]
	}
	endpoints, err := b.registry.Watch(context.Background(), name, group)
	if err != nil {
		return nil, err
	}

	var stop = make(chan struct{})
	go func() {
		defer func() {
			_, file, line, _ := runtime.Caller(5)
			if err := recover(); err != nil {
				log.Errorw("recover", "err", err, "line", fmt.Sprintf("%s:%d", file, line))
			}
		}()
		for {
			select {
			case endpoint := <-endpoints:
				var state = resolver.State{
					Addresses:  make([]resolver.Address, 0),
					Attributes: attributes.New(),
				}
				for k, v := range endpoint.Metadata {
					state.Attributes.WithValues(k, v)
				}
				for _, instance := range endpoint.Instances {
					var address resolver.Address
					address.Addr = instance.Address()
					address.ServerName = endpoint.Name
					address.Attributes = attributes.New()
					for k, v := range instance.Metadata {
						address.Attributes.WithValues(k, v)
					}
					state.Addresses = append(state.Addresses, address)
				}
				cc.UpdateState(state)
			case <-stop:
				return
			}
		}
	}()

	return &baseResolver{
		stop: stop,
	}, nil
}

// Scheme ...
func (b baseBuilder) Scheme() string {
	return b.registry.Name()
}

type baseResolver struct {
	stop chan struct{}
}

// ResolveNow ...
func (b *baseResolver) ResolveNow(options resolver.ResolveNowOptions) {}

// Close ...
func (b *baseResolver) Close() { b.stop <- struct{}{} }
